package org.ipp4j.core;

import java.io.ByteArrayInputStream;
import java.io.DataInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.SequenceInputStream;
import java.nio.ByteBuffer;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;

import org.ipp4j.core.standard.IppConstants;
import org.ipp4j.core.standard.Operation;
import org.ipp4j.core.standard.State;
import org.ipp4j.core.standard.Status;
import org.ipp4j.core.standard.Tag;
import org.ipp4j.core.standard.Version;

public class IppUtils {

	private static final byte[] EMPTY_BYTE = new byte[0];


	/**
	 * <p>
	 * Encode a <code>IppMessage</code> to a byte array.
	 * 
	 * @param message
	 * @return
	 */
	public static byte[] encode(IppMessage message) {
		ByteBuffer buffer = ByteBuffer.allocate(IppConstants.MAX_LENGTH);
		buffer.put(message.getVersion().toByteArray());
		if (message instanceof IppRequest) {
			buffer.put(((IppRequest) message).getOperation().toByteArray());
		} else {
			buffer.put(((IppResponse) message).getStatus().toByteArray());
		}
		buffer.putInt(message.getRequestId());

		Map<AttributeGroup, Collection<Attribute>> attributeGroups = message
				.getAttributeGroups();
		if (!attributeGroups.isEmpty()) {

			for (Entry<AttributeGroup, Collection<Attribute>> group : attributeGroups
					.entrySet()) {
				Tag groupTag = Tag.valueOf(group.getKey().name());
				buffer.put(groupTag.toByte());
				for (Attribute attribute : group.getValue()) {
					for (Entry<String, List<byte[]>> attributeSubGroup : attribute
							.getNameValuePairs().entrySet()) {
						for (byte[] value : attributeSubGroup.getValue()) {
							buffer.put(attribute.getValueTag().toByte());
							byte[] name = attributeSubGroup.getKey().getBytes();
							buffer.putShort((short) name.length);
							if (name.length > 0) {
								buffer.put(name);
							}
							buffer.putShort((short) value.length);
							if (value.length > 0) {
								buffer.put(value);
							}
						}
					}
				}
			}
		}
		buffer.put(Tag.END.toByte());

		int current = buffer.position();
		buffer.rewind();
		byte[] result = new byte[current];
		buffer.get(result, 0, current);
		return result;
	}

	public static IppResponse decodeAsResponse(InputStream stream)
			throws IOException {
		DataInputStream reader = new DataInputStream(stream);

		Version version = Version.valueOf(reader.readShort());
		Status operation = Status.valueOf(reader.readShort());
		int requestId = reader.readInt();

		IppResponse response = new IppResponse(version, operation, requestId);

		decodeAttributes(reader, response);
		response.setDataStream(stream);
		return response;
	}

	/**
	 * <p>
	 * Decode a <code>IppRequest</code> from the given <code>InputStream</code>.
	 * 
	 * @param stream
	 * @return
	 * @throws IOException
	 */
	public static IppRequest decodeAsRequest(InputStream stream)
			throws IOException {
		DataInputStream reader = new DataInputStream(stream);

		Version version = Version.valueOf(reader.readShort());
		Operation operation = Operation.valueOf(reader.readShort());
		int requestId = reader.readInt();

		IppRequest request = new IppRequest(version, operation, requestId);

		decodeAttributes(reader, request);
		request.setDataStream(stream);
		return request;
	}

	private static void decodeAttributes(DataInputStream reader,
			IppMessage message) throws IOException {
		State currentState = State.HEADER;

		AttributeGroup group = null;
		Map<Tag, Attribute> attributes = null;
		Tag valueTag = null;

		while (currentState != State.DATA) {
			byte valueTagCode = reader.readByte();
			valueTag = Tag.valueOf(valueTagCode);
			if (valueTagCode >= State.IDLE.getCode()
					&& valueTagCode <= State.DATA.getCode()) {

				if (valueTagCode == State.DATA.getCode()) {
					if (attributes != null) {
						message.getAttributeGroups().put(group,
								attributes.values());
					}
					currentState = State.DATA;
				}
			}

			if (currentState != State.DATA) {
				if (valueTag.isGroupTag()) {
					if (attributes != null) {
						// message.getAttributeGroups().put(group,
						// attributes.values());
						attributes = new HashMap<Tag, Attribute>();
					}

					if (attributes == null) {
						attributes = new HashMap<Tag, Attribute>();
					}
					group = AttributeGroup.valueOf(valueTag.name());
					valueTagCode = reader.readByte();
					valueTag = Tag.valueOf(valueTagCode);
				}

				String name = new String(extractDefineBytes(reader));
				byte[] value = extractDefineBytes(reader);

				message.addAttribute(group,
						new Attribute(valueTag, name, value));

				if (attributes.containsKey(valueTag)) {
					attributes.get(valueTag).updateValue(name, value);
				} else {
					attributes.put(valueTag, new Attribute(valueTag, name,
							value));
				}
			}
		}
	}

	private static byte[] extractDefineBytes(DataInputStream reader)
			throws IOException {
		short valueLength = reader.readShort();
		if (valueLength == 0) {
			return EMPTY_BYTE;
		}
		byte[] result = new byte[valueLength];
		reader.read(result);
		return result;
	}

	public static byte[] convert(int value) {
		return new byte[] { (byte) (value >>> 32), (byte) (value >>> 16),
				(byte) (value >>> 8), (byte) (value) };
	}

	public static byte[] convert(short value) {
		return new byte[] { (byte) (value >>> 8), (byte) (value) };
	}

	public static InputStream createStream(IppMessage message) {
		byte[] encodedBytes = encode(message);
		ByteArrayInputStream headerStream = new ByteArrayInputStream(
				encodedBytes);
		if (message.getDataStream() == null) {
			return headerStream;
		} else {
			return new SequenceInputStream(headerStream,
					message.getDataStream());
		}
	}

}
